Skip to content

Make orjson an optional dependency (closes #423)#454

Open
ShivamShrivastava18 wants to merge 1 commit into
plotly:masterfrom
ShivamShrivastava18:fix/orjson-optional
Open

Make orjson an optional dependency (closes #423)#454
ShivamShrivastava18 wants to merge 1 commit into
plotly:masterfrom
ShivamShrivastava18:fix/orjson-optional

Conversation

@ShivamShrivastava18
Copy link
Copy Markdown

Closes #423.

Makes orjson an optional dependency with a stdlib json fallback, so users who can't (or don't want to) install orjson — e.g. on Python 3.14t, in minimal/locked-down environments, or just to reduce the dependency footprint — can install Kaleido without it.

What changes for users

pip install kaleido            # stdlib JSON path (smaller install, no orjson)
pip install kaleido[orjson]    # fast path with orjson (unchanged behavior)

The default install no longer pulls orjson. Anyone who wants the speed benefit opts in via the new orjson extra.

Files touched

  • src/py/pyproject.tomlorjson>=3.10.15 moved from dependencies to [project.optional-dependencies] as orjson = ["orjson>=3.10.15"].
  • src/py/kaleido/_kaleido_tab/_tab.py — adds _StdlibJSONEncoder (a json.JSONEncoder subclass that mirrors _orjson_default by calling .tolist() on NumPy-like objects) and _serialize_spec(spec) (picks orjson when present, stdlib otherwise). The single call site in _calc_fig becomes spec_str = _serialize_spec(spec).
  • src/py/kaleido/mocker/_utils.py — same import-guard pattern; load path now reads as bytes when orjson is available, as utf-8 text when it isn't. The yield loop is moved out of the with path.open(...) block so it runs the same way under both branches (file is closed sooner; behavior visible to consumers is unchanged).

How I verified

  • pip install -e . → orjson absent → kaleido._kaleido_tab._tab.orjson is None → stdlib path taken
  • pip install -e ".[orjson]" → orjson present → fast path taken
  • Round-trip test on a representative spec (np.arange, np.linspace, np.array(["a", ...]), np.int32(8) nested) showed both paths produce semantically equivalent JSON after json.loads: identical Python objects. Outputs differ only in whitespace (orjson is compact; stdlib uses default separators) — both are valid input for the JS-side JSON.parse.
  • ruff check — clean on all three files
  • ruff format --check — clean
  • The pure-Python tests (tests/test_utils.py, tests/test_path_tools.py, tests/test_placeholder.py) pass. The browser-dependent tests in tests/test_encoder_one_off.py etc. couldn't be run locally without Chrome; full CI will exercise them.

Notes for review

  • One subtle behavior change in mocker/_utils.py: the for f, w, h, s in itertools.product(...) yield loop was originally nested inside with path.open(...), so the JSON file stayed open during iteration. The new structure closes the file immediately after parsing and then iterates. The yielded data is identical; this is purely a resource-management improvement.
  • The stdlib JSONEncoder cannot match orjson's exact byte-level output (no OPT_SERIALIZE_NUMPY, different whitespace). The downstream consumer is JSON.parse(specStr) on the JS side, which is whitespace-tolerant — semantic equivalence is what matters and is verified.

AI disclosure

This change was authored with Claude Code assistance. I read the full diff, ran the verification steps above myself, and can explain every change.

Move orjson from required dependencies to a 'orjson' optional extra,
with a stdlib json fallback in _kaleido_tab/_tab.py and mocker/_utils.py.

Users who want the fast path:  pip install kaleido[orjson]
Default install no longer pulls orjson, which fixes installation on
Python 3.14t and in environments that prefer minimal dependencies.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove ORJSON hard dep for 3.14t's sake

1 participant